﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Ahresty
{
    /// <summary>
    /// 同一部材番号でグルーピングした要素を操作するクラス
    /// </summary>
    internal class NewGroupUnitHandler
    {
        /// <summary>
        /// 孤立要素を修正する
        /// </summary>
        internal static void FixIsolatedUnits(ref IN3DData in3dData, byte targetMaterialID, byte airMaterialID)
        {
            int[, ,] groupData = new int[in3dData.MaxX, in3dData.MaxY, in3dData.MaxZ];

            //P1：グルーピング処理
            Console.WriteLine(String.Format("P1 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            NewGroupUnitHandler.GroupUnits(in3dData, ref groupData);

            //P2：各部材番号の最大グループを検出
            Console.WriteLine(String.Format("P2 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            List<int> mainGroupIDList = NewGroupUnitHandler.GetMainGroups(in3dData, groupData);
            
            //P3：最大グループ以外の要素をTEMPの部材番号に変更
            Console.WriteLine(String.Format("P3 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            NewGroupUnitHandler.MarkTempUnits(mainGroupIDList, ref in3dData, ref groupData);

            //P4：金型部材に隣接するTEMPグループの要素を金型に修正；その他は空気層に修正
            Console.WriteLine(String.Format("P5 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            NewGroupUnitHandler.ConnectIsolatedUnits(ref in3dData, groupData, targetMaterialID, airMaterialID);
        }

        #region Main Process

        /// <summary>
        /// 要素をグルーピングする
        /// </summary>
        private static void GroupUnits(IN3DData in3dData, ref int[, ,] groupData)
        {
            NewGroupUnitHandler.XYZCheck(in3dData, ref groupData);
            NewGroupUnitHandler.ZXYCheck(in3dData, ref groupData);
        }

        /// <summary>
        /// 各部材番号の最大グループを検出する
        /// </summary>
        private static List<int> GetMainGroups(IN3DData in3dData, int[, ,] groupData)
        {
            List<GroupInfo> groupList = new List<GroupInfo>();

            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                    {
                        byte mid = in3dData.Data[x, y, z];
                        int gid = groupData[x, y, z];

                        GroupInfo gi = groupList.FirstOrDefault(g => g.GroupIndex == gid && g.MaterialID == mid);
                        if (gi == null)
                            groupList.Add(new GroupInfo { MaterialID = mid, GroupIndex = gid, UnitCount = 1, });
                        else
                            gi.UnitCount++;
                    }

            var mainGroups = (from g in groupList group g by g.MaterialID into g2 select new { MaterialID = g2.Key, UnitCount = g2.Max(g4 => g4.UnitCount), }).ToArray();

            List<int> mainGroupIDList = new List<int>();
            foreach (var group in groupList)
                if (mainGroups.Count(g => g.MaterialID == group.MaterialID && g.UnitCount == group.UnitCount) > 0)
                    mainGroupIDList.Add(group.GroupIndex);

            return mainGroupIDList;
        }

        /// <summary>
        /// 最大グループ以外の要素にテンポラリの部材番号を付ける
        /// </summary>
        private static void MarkTempUnits(List<int> mainGroupIDList, ref IN3DData in3dData, ref int[, ,] groupData)
        {
            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                        if (!mainGroupIDList.Contains(groupData[x, y, z]))
                        {
                            groupData[x, y, z] = -1;
                            in3dData.Data[x, y, z] = (byte)'T';
                        }
        }

        /// <summary>
        /// 金型部材に隣接するテンポラリグループの要素を金型に修正し、その他は空気層に修正する
        /// </summary>
        private static void ConnectIsolatedUnits(ref IN3DData in3dData, int[, ,] groupData, byte targetMaterialID, byte airMaterialID)
        {
            List<int> connectedGroupList = new List<int>();

            short maxX = in3dData.MaxX;
            short maxY = in3dData.MaxY;
            short maxZ = in3dData.MaxZ;

            for (short z = 0; z < maxZ; z++)
                for (short y = 0; y < maxY; y++)
                    for (short x = 0; x < maxX; x++)
                    {
                        if (in3dData.Data[x, y, z] != 'T')
                            continue;

                        int groupIndex = groupData[x, y, z];
                        if (connectedGroupList.Contains(groupIndex))
                            continue;

                        byte xPlus = x + 1 == maxX ? (byte)' ' : in3dData.Data[x + 1, y, z];
                        byte xMinus = x - 1 < 0 ? (byte)' ' : in3dData.Data[x - 1, y, z];
                        byte yPlus = y + 1 == maxY ? (byte)' ' : in3dData.Data[x, y + 1, z];
                        byte yMinus = y - 1 < 0 ? (byte)' ' : in3dData.Data[x, y - 1, z];
                        byte zPlus = z + 1 == maxZ ? (byte)' ' : in3dData.Data[x, y, z + 1];
                        byte zMinus = z - 1 < 0 ? (byte)' ' : in3dData.Data[x, y, z - 1];

                        if (xPlus == targetMaterialID || xMinus == targetMaterialID ||
                            yPlus == targetMaterialID || yMinus == targetMaterialID ||
                            zPlus == targetMaterialID || zMinus == targetMaterialID)
                            connectedGroupList.Add(groupIndex);
                    }

            for (short z = 0; z < maxZ; z++)
                for (short y = 0; y < maxY; y++)
                    for (short x = 0; x < maxX; x++)
                    {
                        if (in3dData.Data[x, y, z] != 'T')
                            continue;

                        int groupIndex = groupData[x, y, z];

                        if (connectedGroupList.Contains(groupIndex))
                            in3dData.Data[x, y, z] = targetMaterialID;
                        else
                            in3dData.Data[x, y, z] = airMaterialID;
                    }
        }

        #endregion

        #region Grouping Process

        /// <summary>
        /// N3
        /// </summary>
        private static void XYInitialize(ref int[, ,] groupData, ref int groupIndex, int maxX, int maxY, int z, IN3DData in3dData)
        {
            for (short y = 0; y < maxY; y++)//Y+
            {
                for (short x = 0; x < maxX; x++)
                {
                    if (x == 0)
                        groupData[x, y, z] = groupIndex;
                    else
                    {
                        if (in3dData.Data[x, y, z] == in3dData.Data[x - 1, y, z])
                            groupData[x, y, z] = groupData[x - 1, y, z];
                        else
                        {
                            groupIndex++;
                            groupData[x, y, z] = groupIndex;
                        }
                    }
                }

                if (y > 0)
                    for (short x = 0; x < maxX; x++)
                        if (in3dData.Data[x, y, z] == in3dData.Data[x, y - 1, z] && groupData[x, y, z] != groupData[x, y - 1, z])
                        {
                            int a = groupData[x, y, z];
                            int b = groupData[x, y - 1, z];

                            if (a > b)
                                for (short x2 = 0; x2 < maxX; x2++)
                                    if (groupData[x2, y, z] == a)
                                        groupData[x2, y, z] = b;
                        }
                groupIndex++;
            }

            for (int y = maxY - 2; y >= 0; y--)//Y-
                for (int x = 0; x < maxX; x++)
                    if (in3dData.Data[x, y, z] == in3dData.Data[x, y + 1, z] && groupData[x, y, z] != groupData[x, y + 1, z])
                    {
                        int a = groupData[x, y, z];
                        int b = groupData[x, y + 1, z];

                        if (a > b)
                            for (short x2 = 0; x2 < maxX; x2++)
                                if (groupData[x2, y, z] == a)
                                    groupData[x2, y, z] = b;
                    }
        }

        /// <summary>
        /// N3
        /// </summary>
        private static void XYCheck(ref int[, ,] groupData, int maxX, int maxY, int z, IN3DData in3dData)
        {
            for (short y = 0; y < maxY; y++)//X+
                for (short x = 1; x < maxX; x++)
                    if (in3dData.Data[x, y, z] == in3dData.Data[x - 1, y, z] && groupData[x, y, z] != groupData[x - 1, y, z])
                    {
                        int a = groupData[x, y, z];
                        int b = groupData[x - 1, y, z];

                        if (a > b)
                            groupData[x, y, z] = groupData[x - 1, y, z];
                        else
                            for (short x2 = 0; x2 < maxX; x2++)
                                if (groupData[x2, y, z] == b)
                                    groupData[x2, y, z] = a;

                    }

            for (short y = 1; y < maxY; y++)//Y+
                for (short x = 0; x < maxX; x++)
                    if (in3dData.Data[x, y, z] == in3dData.Data[x, y - 1, z] && groupData[x, y, z] != groupData[x, y - 1, z])
                    {
                        int a = groupData[x, y, z];
                        int b = groupData[x, y - 1, z];

                        if (a > b)
                            for (short x2 = 0; x2 < maxX; x2++)
                                if (groupData[x2, y, z] == a)
                                    groupData[x2, y, z] = b;
                    }

            for (int y = maxY - 2; y >= 0; y--)//Y-
                for (int x = 0; x < maxX; x++)
                    if (in3dData.Data[x, y, z] == in3dData.Data[x, y + 1, z] && groupData[x, y, z] != groupData[x, y + 1, z])
                    {
                        int a = groupData[x, y, z];
                        int b = groupData[x, y + 1, z];

                        if (a > b)
                            for (short x2 = 0; x2 < maxX; x2++)
                                if (groupData[x2, y, z] == a)
                                    groupData[x2, y, z] = b;
                    }
        }

        /// <summary>
        /// N3
        /// </summary>
        private static void XZCheck(ref int[, ,] groupData, int maxX, int maxZ, int y, IN3DData in3dData)
        {
            for (short x = 0; x < maxX; x++)
                for (short z = 1; z < maxZ; z++)
                    if (in3dData.Data[x, y, z] == in3dData.Data[x, y, z - 1] && groupData[x, y, z] != groupData[x, y, z - 1])
                    {
                        int a = groupData[x, y, z];
                        int b = groupData[x, y, z - 1];

                        if (a > b)
                            groupData[x, y, z] = groupData[x, y, z - 1];
                        else
                            for (short z2 = 0; z2 < maxZ; z2++)
                                if (groupData[x, y, z2] == b)
                                    groupData[x, y, z2] = a;

                    }

            for (short x = 1; x < maxX; x++)//X+
                for (short z = 0; z < maxZ; z++)
                    if (in3dData.Data[x, y, z] == in3dData.Data[x - 1, y, z] && groupData[x, y, z] != groupData[x - 1, y, z])
                    {
                        int a = groupData[x, y, z];
                        int b = groupData[x - 1, y, z];

                        if (a > b)
                            for (short z2 = 0; z2 < maxZ; z2++)
                                if (groupData[x, y, z2] == a)
                                    groupData[x, y, z2] = b;
                    }

            for (int x = maxX - 2; x >= 0; x--)//X-
                for (int z = 0; z < maxZ; z++)
                    if (in3dData.Data[x, y, z] == in3dData.Data[x + 1, y, z] && groupData[x, y, z] != groupData[x + 1, y, z])
                    {
                        int a = groupData[x, y, z];
                        int b = groupData[x + 1, y, z];

                        if (a > b)
                            for (short z2 = 0; z2 < maxZ; z2++)
                                if (groupData[x, y, z2] == a)
                                    groupData[x, y, z2] = b;
                    }
        }

        private static void XYZCheck(IN3DData in3dData, ref int[, ,] groupData)
        {
            int groupIndex = 0;
            short maxX = in3dData.MaxX;
            short maxY = in3dData.MaxY;
            short maxZ = in3dData.MaxZ;

            XYInitialize(ref groupData, ref groupIndex, maxX, maxY, 0, in3dData);
            for (int z = 1; z < maxZ; z++)
            {
                XYInitialize(ref groupData, ref groupIndex, maxX, maxY, z, in3dData);
                for (short y = 0; y < maxY; y++)
                {
                    for (short x = 0; x < maxX; x++)
                    {
                        if (in3dData.Data[x, y, z] == in3dData.Data[x, y, z - 1] && groupData[x, y, z] != groupData[x, y, z - 1])
                        {
                            int a = groupData[x, y, z];
                            int b = groupData[x, y, z - 1];

                            if (a > b)
                                groupData[x, y, z] = b;
                        }
                    }
                }
                XYCheck(ref groupData, maxX, maxY, z, in3dData);
            }

            XYCheck(ref groupData, maxX, maxY, maxZ - 1, in3dData);
            for (int z = maxZ - 2; z >= 0; z--)
            {
                for (short y = 0; y < maxY; y++)
                {
                    for (short x = 0; x < maxX; x++)
                    {
                        if (in3dData.Data[x, y, z] == in3dData.Data[x, y, z + 1] && groupData[x, y, z] != groupData[x, y, z + 1])
                        {
                            int a = groupData[x, y, z];
                            int b = groupData[x, y, z + 1];

                            if (a > b)
                                groupData[x, y, z] = b;
                        }
                    }
                }
                XYCheck(ref groupData, maxX, maxY, z, in3dData);
            }
            //NewGroupUnitHandler.Output(groupData, @"D:\Documents\Visual Studio 2010\Projects\Ahresty\Ahresty\bin\Release\temp\in3d\ult1.in3d");
        }

        private static void ZXYCheck(IN3DData in3dData, ref int[, ,] groupData)
        {
            int maxX = in3dData.MaxX;
            int maxY = in3dData.MaxY;
            int maxZ = in3dData.MaxZ;

            for (int y = 1; y < maxY; y++)
            {
                for (int x = 0; x < maxX; x++)
                    for (int z = 0; z < maxZ; z++)
                        if (in3dData.Data[x, y, z] == in3dData.Data[x, y - 1, z] && groupData[x, y, z] != groupData[x, y - 1, z])
                        {
                            int a = groupData[x, y, z];
                            int b = groupData[x, y - 1, z];

                            if (a > b)
                                groupData[x, y, z] = b;
                        }
                XZCheck(ref groupData, maxX, maxZ, y, in3dData);
            }

            XZCheck(ref groupData, maxX, maxZ, maxY - 1, in3dData);
            for (int y = maxY - 2; y >= 0; y--)
            {
                for (int x = 0; x < maxX; x++)
                    for (int z = 0; z < maxZ; z++)
                        if (in3dData.Data[x, y, z] == in3dData.Data[x, y + 1, z] && groupData[x, y, z] != groupData[x, y + 1, z])
                        {
                            int a = groupData[x, y, z];
                            int b = groupData[x, y + 1, z];

                            if (a > b)
                                groupData[x, y, z] = b;
                        }
                XZCheck(ref groupData, maxX, maxZ, y, in3dData);
            }
            //NewGroupUnitHandler.Output(groupData, @"D:\Documents\Visual Studio 2010\Projects\Ahresty\Ahresty\bin\Release\temp\in3d\ult1.in3d");
        }

        #endregion

        internal static void Output(int[, ,] groupData, string destFilePath)
        {
            List<string> fileContents = new List<string>();

            for (short z = 0; z < groupData.GetLength(2); z++)
            {
                for (short y = 0; y < groupData.GetLength(1); y++)
                {
                    string line = String.Empty;

                    for (int x = 0; x < groupData.GetLength(0); x++)
                        line += groupData[x, y, z].ToString("00") + " ";

                    fileContents.Add(line);
                }

                fileContents.Add(String.Empty);
            }

            File.WriteAllLines(destFilePath, fileContents.ToArray());
        }
    }
}
